////////////////      界面或菜单跳转框架程序例程                                         ////////////
////////////////      ifs_class_flash_pt_example   V20201025                          ////////////
////////////////      by changser    20201025                                         ////////////
////////////////      changser@139.com                                                ////////////
//说明：
//由 ifs_class_flash_serial_example.ino和ifs_class_dis_example.ino改编而来，
//目的是将ifs_class应用于PT之下。
//ifs_class有两个版本，flash版本适用于arduino或AVR，ifs跳转数组存于falsh之中。
//如果是别的单片机，内存够大的话，存于ram之中，直接将#include "ifs_class_flash.h"换成
//#include "ifs_class.h"，然后定义界面结构体数组时不要加上program，见程序。
//功能：
//界面跳转演示，共三个界面，通过串口文本或LED屏（TM1628）来显示各界面进入、即时运行和退出的时机
//控制也是通过输入串口发送或按键来实现。上翻w,下翻s
//及：keys[0]当作上翻键，keys[1]当作下翻键。原有电脑键盘的w\s上下翻仍有效。
//串口显示：
//enter1............................................
//running1..
//bye1..
//enter2..........................................
//...
//LED显示：
//...


//任务分配：
//任务1：tm1688驱动，每50ms执行一次，主要是按键取得需要时间限定，显示是写入芯片TM1628后不需要管的。
//任务2：ifs_class涮新，包括跳转命令取得及跳转命令判断处理。都需要不断涮新的。
//       因为跳转命令的取值的更新一部分由按键取得，也与任务1同步，所以涮新时间也为50ms。
//       因为跳转处理的各个跳转程序很可能耗时长，且不一致，延时次数多了之后，可能造成与任务1的50ms不同步，
//       不知有多少概率造成按键命令丢失。所以由任务1发信号传递。

#include "ifs_class_flash.h"

/////////////////////////////////////////////////////////////////////////////////////////////
//内部函数声明：
//功能：根据串口命令解析得到界面切换命令
//输入：串口缓冲区
//输出：static enum switchCMDenum类型
static enum switchCMDenum getIfsCMD();

//功能：界面1的进入、即时运行、退出函数
static void enter1();
static void run1();
static void exit1();

//功能：界面2的进入、即时运行、退出函数
static void enter2();
static void run2();
static void exit2();

//功能：界面3的进入、即时运行、退出函数
static void enter3();
static void run3();
static void exit3();

//////////////////////////////////////////////////////////////////////////////////////////////////
//变常量定义：
//界面结构体数组
#define IFSNUM  3
static const   interfaceStr   interfaceTable[IFSNUM] PROGMEM
= {
  //{0,{0,0,0,0,0,0},NULL,NULL,NULL},标准格式，0或空为初始值
  {1, {3, 2, 0, 0, 0, 0, 0, 0, 0, 0}, enter1, run1, exit1}, //缺省为开机的入口界面1。必须是数组第一个（下标为0）
  {2, {1, 3, 0, 0, 0, 0, 0, 0, 0, 0}, enter2, run2, exit2},
  {3, {2, 1, 0, 0, 0, 0, 0, 0, 0, 0}, enter3, run3, exit3}
};

//类IFS实例为对象ifs
IFS_F ifs_f = IFS_F(&interfaceTable[0], IFSNUM);


////////////////////////////以下是按键部分//////////////////////////////////////////////////
#include "dis_key.h"

//设置物理LED显示器（下标+1）与逻辑LED显示器序号（元素）的映射表。即排位顺序。一定要是9个？实际没有的怎么处理？
static const uint8_t dis_table1[9] = {6, 5, 4 , 3, 2, 1, 7, 8, 9 };

//设置物理按键序号（下标+1）与逻辑按键序号（元素）的映射表（1~20）。即排位顺序。最大20个元素。
static  const uint8_t key_table1[] = {1, 4, 5, 8};

APP_TM1628 app_tm1628 = APP_TM1628(11, 12, 13);//STB=11,DIO=12,CLK=13


/////////////////////以下是PT部分//////////////////////////////////////////////////////////////////
#define PT_USE_TIMER
#define PT_USE_SIGNAL
#define LC_INCLUDE "lc-addrlabels.h"

#include "pt.h"

static struct pt thread1, thread2, thread3;
static int thread1_entry(struct pt *pt);
static int thread2_entry(struct pt *pt);
static int thread3_entry(struct pt *pt);







/////////////////////////////////////////////////////////////////////////////////////////////
//arduino开机初始化，系统默认。
void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  Serial.println("hello,welcome");
  
//ifs不用初始化

//tm1628类初始化，也可不初始化，则用的是缺省的符合缺省原理图的按键和LED排列。
 //若LED显示器排列不是和标准电路相一致，则建立映射，并告知
  app_tm1628.set_dis_table( dis_table1);  //dis_table1 <=> &dis_table1[0]
  //或KEY键排列不是和标准电路相一致，则建立映射，并告知
  app_tm1628.set_key_table(key_table1, sizeof(key_table1));
  //所有显示闪一下（1s），包括指示灯，再都清空
  app_tm1628.to_led_flash();

 //Initialize the Threads
  PT_INIT(&thread1); //只对结构thread1中的lc元素初台化，对t和signal并不处理
  PT_INIT(&thread2);
  PT_INIT(&thread3);


  
}

//arduino循环，系统默认。
void loop() {

 //将任务与pt结构体变量关联，且不断从让出点再进入。
  thread1_entry(&thread1);
  thread2_entry(&thread2);
  thread3_entry(&thread3);
}

//任务1：每50ms取键盘状态更新keys[]，其它任务查询keys[]就好。
//也可以在此任务中判断按键动作再发信号给相应的任务（其它任务等信号）。
static int thread1_entry(struct pt *pt)
{
  PT_BEGIN(pt);
  while (1)
  {
    app_tm1628.get_key();
    PT_SEND_SIGNAL(&thread2);
    PT_WAIT_MS(pt, 50);
  }
  PT_END(pt);
}

//任务2：ifs得命令和处理跳转
static int thread2_entry(struct pt *pt)
{
  PT_BEGIN(pt);
 while (1)             //这个循环体可以不要。因为协程切换的本质在于任务被不断调用（比如loop()中），PT_BEGIN(pt);是switch，出让点是case分支。
                         //有没有这个while(1)，下次都会在出让点继续，因为每次loop()被调用又会重复switch case 来跳转。
                         //但是最好还是要，因为如果while(1)前面如果还有语句，那么执行次数是不一样的；
                         //另外，要了的话，看起来像是一个任务，更好理解，以后移植到OS也更方便。
  {
    PT_WAIT_SIGNAL(pt);   //与任务1同步，每50ms得键后更新跳转命令及执行跳转。不放在任务1中是因为跳转任务时间不确定。
    ifs_f.switchCMD = getIfsCMD();  //命令的更新，如果所有任务中具有统一的定义，可以在这里集中处理。
                                    //但如果有的任务中相同按键有其他作用，可以放在每个界面运行分支中来处理。
    ifs_f.doSwitchCMD();  //执行窗口解析函数
  }
  PT_END(pt);
}


//任务3：等发信号过来，等到了就闪两下。等时不占用单片机时间；灯亮或灭0.5s期间也没有占用单片机时间，出让控制权了。
static int thread3_entry(struct pt *pt)
{
  PT_BEGIN(pt);
  while (1)    
  {
  
      PT_YIELD(pt); //Check the other events.关键就在于这个出让
   
  }
  PT_END(pt);
}

//////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////
//函数实现：
//功能：根据串口命令或按键解析得到界面切换命令
//输入：串口缓冲区
//输出：static enum switchCMDenum类型
static enum switchCMDenum getIfsCMD()
{
  enum switchCMDenum temp = SNONE;
  if (Serial.available() > 0)
  {
    switch (Serial.read())
    {
      case 'w':
        temp = SUP;
        break;
      case 's':
        temp = SDOWN;
        break;
      default:
        temp = SNONE;
        break;
    }
  };
  if (temp == SNONE)
  {
    if (app_tm1628.keys[0].key_buffer == KEY_DOWN) temp = SUP;
    else  if (app_tm1628.keys[1].key_buffer == KEY_DOWN) temp = SDOWN;
  };
  return temp;
}
static void enter1()
{
  //界面初始化，如重生成新屏、参数初始化
 // Serial.println("hello");
  Serial.println("enter1............................................");
  app_tm1628.to_led_seg(0b10000000, LED5);
  app_tm1628.to_led_char('-', NO_DOT, LED6);
  app_tm1628.to_led(1.456, DOT2, LED4, 4);
}

static void run1()
{
  //显示涮新或其他实时动作
  static unsigned int  i = 0;
  i++;
  if (i >= 20)
  {
    i = 0;
    Serial.println("running1..");
  }
}

static void exit1()
{
  //退出界面时参数销毁或保存等
  Serial.println("bye1");
}

static void enter2()
{
  Serial.println("enter2...................................................");
  app_tm1628.to_led((int16_t)5432, NO_DOT, LED4, 4 );
}

static void run2()
{
  static unsigned int i = 0;
  i++;
  if (i >= 20)
  {
    i = 0;
    Serial.println("running2..");
  }
}

static void exit2()
{
  Serial.println("bye2");
}

static void enter3()
{
  Serial.println("enter3.....................................................");
  app_tm1628.to_led((int16_t)76543, NO_DOT, LED5, 5);
}

static void run3()
{
  static unsigned int i = 0;
  i++;
  if (i >= 20)
  {
    i = 0;
    Serial.println("running3..");
  }
}

static void exit3()
{
  Serial.println("bye3");
}
